package re56.layer;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import re56.equipment.Equipment;
import re56.equipment.EvolvedNodeB;
import re56.equipment.UserEquipment;
import re56.packet.PDCCHPacket;
import re56.packet.PDSCHPacket;
import re56.packet.PUCCHPacket;
import re56.packet.PUSCHPacket;


// A RadioLayer connect one EvolvedNodeB Physical Layer to Many UEs Physical Layer
public class RadioLayer extends Thread
{
	// as we handle only the FDD mode,
	// a channel can be in UL or DL mode (but not both)
	public static final int UL_MODE = 0;
	public static final int DL_MODE = 1;
	
	// more than the UE sensitivity.
	public static final int OUT_OF_REACH = -120;
	
	
	private EvolvedNodeB eNB;
	private List<UserEquipment> UEs = new ArrayList<UserEquipment>();
	
	// in Mhz
	// TODO: check if it match one of the E-UTRAN Bands
	private int frequency;
	
	// in Khz
	// can be : 1400, 3000, 5000, 10000, 15000, 20000
	// TODO: Check the value, maybe create an exception.
	private int bandwidth;
	
	
	// the number of ResourceBlocks.
	private int RBnumber;
	
	
	// the mode. UL or DL.
	private int mode;
	
	
	// 2 DownLink Channels
	private PDCCHPacket PDCCH;
	private PDSCHPacket PDSCH;
	
	// 2 Uplink Channels
	private PUCCHPacket PUCCH;
	private PUSCHPacket PUSCH;
	
	
	public RadioLayer(int frequency, int bandwidth, int mode)
	{
		this.frequency = frequency;
		this.bandwidth = bandwidth;
		this.mode = mode;
		
		// depending on the bandwidth, create the right number of Resource Blocks.
		switch(bandwidth)
		{
			case 1400:
			{
				RBnumber = 6;
				break;
			}
			case 3000:
			{
				RBnumber = 15;
				break;
			}
			case 5000:
			{
				RBnumber = 25;
				break;
			}
			case 10000:
			{
				RBnumber = 50;
				break;
			}
			case 15000:
			{
				RBnumber = 75;
				break;
			}
			case 20000:
			{
				RBnumber = 100;
				break;
			}
			default:
			{
				// error !
				RBnumber = 0;
				break;
			}
		}
	}
	
	
	public int getFrequency()
	{
		return frequency;
	}
	
	// return the number of Resource Blocks Available.
	public int getRBNumber()
	{
		return RBnumber;
	}
	
	public int getBandwidth()
	{
		return bandwidth;
	}
	
	
	public int getFDDMode()
	{
		return mode;
	}
	
	
	public void useRadioLayer(EvolvedNodeB enb)
	{
		eNB = enb;
	}
	
	
	public void useRadioLayer(UserEquipment ue)
	{
		UEs.add(ue);
	}
	
	
	public void writeInPDSCH(PDSCHPacket p)
	{
		// check the Duplex Mode;
		if(mode != DL_MODE)
			return;
		
		// let's register this packet
		PDSCH = p;
		
		Iterator<UserEquipment> it = UEs.iterator();
		while(it.hasNext())
		{
			it.next().getPhysicalLayer().receive();
		}
	}
	
	public void writeInPDCCH(PDCCHPacket p)
	{
		// check the Duplex Mode;
		if(mode != DL_MODE)
			return;
		
		// let's register this packet
		PDCCH = p;
		
		Iterator<UserEquipment> it = UEs.iterator();
		while(it.hasNext())
		{
			it.next().getPhysicalLayer().receive();
		}
	}
	
	
	public PDSCHPacket readInPDSCH()
	{
		if(mode == DL_MODE)
		{
			return PDSCH;
		}
		
		return null;
	}
	
	public PDCCHPacket readInPDCCH()
	{
		if(mode == DL_MODE)
		{
			return PDCCH;
		}
		
		return null;
	}
	
	
	
	
	public void writeInPUSCH(PUSCHPacket p)
	{
		if(mode != UL_MODE)
			return;
		
		PUSCH = p;
		
		eNB.getPhysicalLayer().receive();
	}
	
	public void writeInPUCCH(PUCCHPacket p)
	{
		if(mode != UL_MODE)
			return;
		
		PUCCH = p;
		
		eNB.getPhysicalLayer().receive();
	}

	public PUCCHPacket readInPUCCH()
	{
		if(mode == UL_MODE)
		{
			return PUCCH;
		}
		
		return null;
	}
	
	public PUSCHPacket readInPUSCH()
	{
		if(mode == UL_MODE)
		{
			return PUSCH;
		}
		
		return null;
	}
	
	
	// in dBm
	public double computeRxLevel(Equipment ue)
	{
		if(!UEs.contains(ue))
			return OUT_OF_REACH;
		
		// first compute the distance between the eNB and this UE.
		// distance in meter.
		double distance = ue.getPosition().distance(eNB.getPosition());
		
		// then apply a Free Path Loss, distance in meters, freq in MHz
		double FPL = 20*Math.log10(distance) + 20*Math.log10(frequency) - 27.55;
		
		
		System.out.println("RxLevel Computed eNB "+eNB.getPosition()+" UE "+ue.getMACAddress()+" "+ue.getPosition()+" distance: "+Math.round(distance)+"m value: "+Math.round(eNB.getPhysicalLayer().getPower() - FPL)+"dBm");
		
		
		return eNB.getPhysicalLayer().getPower() - FPL;
	}
	
	
	// in dBm
	public double computeInterferences(Equipment ue)
	{
		if(!UEs.contains(ue))
			return 0;
		
		double interf_mW = 0;
		
		Iterator<UserEquipment> it = UEs.iterator();
		while(it.hasNext())
		{
			UserEquipment _ue = it.next();
			
			if(_ue.getMACAddress().compareTo(ue.getMACAddress()) == 0)
				continue;
			
			// first compute the distance between the eNB and this UE.
			// distance in meter.
			double distance = ue.getPosition().distance(_ue.getPosition());
			
			// then apply a Free Path Loss, distance in meters, freq in MHz
			double FPL = 20*Math.log10(distance) + 20*Math.log10(frequency) - 27.55;
			
			interf_mW += Math.pow(10, (_ue.getPhysicalLayer().getPower() - FPL) / 10.0);
		}
		
		return 10 * Math.log(interf_mW);
	}
	
	
	
	public void run()
	{
		// each 1ms (one sub-frame, 2 Resource Blocks or 1 Scheduled Block)
		while(true)
		{
			if(mode == UL_MODE)
			{
				// in the UL mode, we have to call the EvolvedNodeB PhysicalLayer 'receive' Function.
				eNB.getPhysicalLayer().receive();
			}
			else if(mode == DL_MODE)
			{
				// in the UL mode, we have to call all the UserEquipment PhysicalLayer 'receive' Functions.
				Iterator<UserEquipment> it = UEs.iterator();
				
				while(it.hasNext())
				{
					it.next().getPhysicalLayer().receive();
				}
			}
			
			try {
				// should be 1ms
				Thread.sleep(3000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}
